---
title: Refs and the DOM
description: "Using Refs and DOM elements in ReScript and React"
canonical: "/docs/react/refs-and-the-dom"
section: "Main Concepts"
order: 6
---

# Refs and the DOM

<Intro>

Refs provide a way to access DOM nodes or React elements created within your `make` component function.

</Intro>

In the typical React dataflow, [props](./components-and-props.mdx) are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical dataflow. The child to be modified could be an `React.element`, or it could be a `Dom.element`. For both of these cases, React provides an escape hatch.

A `React.ref` is defined like this:

```res
type t<'value> = { mutable current: 'value }
```

> _Note that the `Ref.ref` should not to be confused with the builtin [ref type](../manual/mutation.mdx), the language feature that enables mutation._

## When to use Refs

There are a few good use cases for refs:

- Managing state that _should not trigger_ any re-render.
- Managing focus, text selection, or media playback.
- Triggering imperative animations.
- Integrating with third-party DOM libraries.

Avoid using refs for anything that can be done declaratively.

## Creating Refs

A React ref is represented as a `React.ref('value)` type, a container managing a mutable value of type `'value`. You can create this kind of ref with the [React.useRef](./hooks-ref.mdx) hook:

```res example
@react.component
let make = () => {
  let clicks = React.useRef(0)

  let onClick = (_) => {
    clicks.current = clicks.current + 1
  };

  <div onClick>
    {Belt.Int.toString(clicks.current)->React.string}
  </div>
}
```

The example above defines a binding `clicks` of type `React.ref<int>`. Note how changing the value `clicks.current` doesn't trigger any re-rendering of the component.

## Accessing Refs

When a ref is passed to an element during render, a reference to the node becomes accessible at the current attribute of the ref.

```res
let value = myRef.current
```

The value of the ref differs depending on the type of the node:

- When the ref attribute is used on an HTML element, the ref passed via `ReactDOM.Ref.domRef` receives the underlying DOM element as its current property (type of `React.ref<Nullable.t<Dom.element>>`)
- In case of interop, when the ref attribute is used on a custom class component (based on JS classes), the ref object receives the mounted instance of the component as its current (not discussed in this document).
- **You may not use the ref attribute on component functions** because they don’t have instances (we don't expose JS classes in ReScript).

Here are some examples:

### Adding a Ref to a DOM Element

This code uses a `React.ref` to store a reference to an `input` DOM node to put focus on a text field when a button was clicked:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
// CustomTextInput.res

@send external focus: Dom.element => unit = "focus"

@react.component
let make = () => {
  let textInput = React.useRef(Nullable.null)

  let focusInput = () =>
    switch textInput.current {
    | Value(dom) => dom->focus
    | Null | Undefined => ()
    }

  let onClick = _ => focusInput()

  <div>
    <input type_="text" ref={ReactDOM.Ref.domRef(textInput)} />
    <input type_="button" value="Focus the text input" onClick />
  </div>
}
```

```js
function CustomTextInput(Props) {
  var textInput = React.useRef(null);
  var focusInput = function () {
    var dom = textInput.current;
    if (dom === null || dom === undefined) {
      return;
    }
    dom.focus();
  };
  var onClick = function (param) {
    focusInput();
  };
  return JsxRuntime.jsxs("div", {
    children: [
      JsxRuntime.jsx("input", {
        ref: Caml_option.some(textInput),
        type: "text",
      }),
      JsxRuntime.jsx("input", {
        type: "button",
        value: "Focus the text input",
        onClick: onClick,
      }),
    ],
  });
}
```

</CodeTab>

A few things happened here, so let's break them down:

- We initialize our `textInput` ref as a `Nullable.null`
- We register our `textInput` ref in our `<input>` element with `ReactDOM.Ref.domRef(textInput)`
- In our `focusInput` function, we need to first verify that our DOM element is set, and then use the `focus` binding to set the focus

React will assign the `current` field with the DOM element when the component mounts, and assign it back to null when it unmounts.

### Refs and Component Functions

In React, you **can't** pass a `ref` attribute to a component function:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
module MyComp = {
  @react.component
  let make = (~ref) => <input />
}

@react.component
let make = () => {
  let textInput = React.useRef(Nullable.null)

  // This will **not** work
  <MyComp ref={ReactDOM.Ref.domRef(textInput)} />
}
```

```js
// Compiler Error:
// Ref cannot be passed as a normal prop. Please use `forwardRef`
// API instead
```

</CodeTab>

The snippet above will not compile and output an error that looks like this: `"Ref cannot be passed as a normal prop. Please use forwardRef API instead."`.

As the error message implies, If you want to allow people to take a ref to your component function, you can use [ref forwarding](./forwarding-refs.mdx) (possibly in conjunction with useImperativeHandle) instead.

## Exposing DOM Refs to Parent Components

In rare cases, you might want to have access to a child’s DOM node from a parent component. This is generally not recommended because it breaks component encapsulation, but it can occasionally be useful for triggering focus or measuring the size or position of a child DOM node.

we recommend to use [ref forwarding](./forwarding-refs.mdx) for these cases. **Ref forwarding lets components opt into exposing any child component's ref as their own**. You can find a detailed example of how to expose a child's DOM node to a parent component in the ref forwarding documentation.

## Callback Refs

React also supports another way to set refs called “callback refs” (`React.Ref.callbackDomRef`), which gives more fine-grain control over when refs are set and unset.

Instead of passing a ref value created by `React.useRef()`, you can pass in a callback function. The function receives the target `Dom.element` as its argument, which can be stored and accessed elsewhere.

**Note:** Usually we'd use `React.Ref.domRef()` to pass a ref value, but for callback refs, we use `React.Ref.callbackDomRef()` instead.

The example below implements a common pattern: using the ref callback to store a reference to a DOM node in an instance property.

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
// CustomTextInput.res

@send external focus: Dom.element => unit = "focus"

@react.component
let make = () => {
  let textInput = React.useRef(Nullable.null)
  let setTextInputRef = element => {
    textInput.current = element
    None
  }

  let focusTextInput = _ => {
    textInput.current->Nullable.forEach(input => input->focus)
  }

  <div>
    <input type_="text" ref={ReactDOM.Ref.callbackDomRef(setTextInputRef)} />
    <input
      type_="button" value="Focus the text input" onClick={focusTextInput}
    />
  </div>
}
```

```js
function CustomTextInput(Props) {
  var textInput = React.useRef(null);
  var setTextInputRef = function (element) {
    textInput.current = element;
  };
  var focusTextInput = function (param) {
    Core__Nullable.forEach(textInput.current, function (input) {
      input.focus();
    });
  };
  return React.createElement(
    "div",
    undefined,
    React.createElement("input", {
      ref: Caml_option.some(setTextInputRef),
      type: "text",
    }),
    React.createElement("input", {
      type: "button",
      value: "Focus the text input",
      onClick: focusTextInput,
    }),
  );
}
```

</CodeTab>

React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts.

You can pass callback refs between components like you can with object refs that were created with `React.useRef()`.

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
// Parent.res

@send external focus: Dom.element => unit = "focus"

module CustomTextInput = {
  @react.component
  let make = (~setInputRef) => {
    <div>
      <input type_="text" ref={ReactDOM.Ref.callbackDomRef(setInputRef)} />
    </div>
  }
}

@react.component
let make = () => {
  let textInput = React.useRef(Nullable.null)
  let setInputRef = element => {
    textInput.current = element
    None
  }

  <CustomTextInput setInputRef/>
}
```

```js
function CustomTextInput(Props) {
  var setInputRef = Props.setInputRef;
  return React.createElement(
    "div",
    undefined,
    React.createElement("input", {
      ref: setInputRef,
      type: "text",
    }),
  );
}

var CustomTextInput = {
  make: CustomTextInput,
};

function Parent(Props) {
  var textInput = React.useRef(null);
  var setInputRef = function (element) {
    textInput.current = element;
  };
  return React.createElement(CustomTextInput, {
    setInputRef: setInputRef,
  });
}
```

</CodeTab>

In the example above, `Parent` passes its ref callback as an `setInputRef` prop to the `CustomTextInput`, and the `CustomTextInput` passes the same function as a special ref attribute to the `<input>`. As a result, the `textInput` ref in Parent will be set to the DOM node corresponding to the `<input>` element in the `CustomTextInput`.
